﻿using MQTTNet.Client.Attributes;
using MQTTNet.Client.Common;
using MQTTNet.Client.Enums;
using MQTTNet.Client.Model;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace MQTTNet.Client.Handles
{
    internal class RouteHandle: IRouteHandle
    {
        private List<RouteHandleModel> routes = new List<RouteHandleModel>();
        public List<RouteHandleModel> Routes { get=> routes; }
        internal RouteHandle(int identity = 0)
          =>this.Init(identity);
        public void Init(int identity = 0)
        {
            List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies()
                .Where(s=>!s.FullName.StartsWith(nameof(System))
                && !s.FullName.StartsWith(nameof(Microsoft))
                && !s.FullName.ToLower().StartsWith("netstandard")).ToList();
            assemblies.ForEach(s =>
            {
                Type[] types = s.GetTypes();
                foreach (var item in types)
                {
                    foreach (var method in item.GetMethods())
                    {
                        var routeAttribute = method.GetCustomAttribute<RouterAttribute>();
                        if (routeAttribute != null&& routeAttribute._identity==identity)
                        {
                            if (!routes.Any(s => s.Topic == routeAttribute.Topic &&  s.QOS == routeAttribute.QOS))
                                routes.Add(new RouteHandleModel()
                                {
                                    ClassName = item.Name,
                                    NameSpace = item.Namespace,
                                    Method = method,
                                    Topic = routeAttribute.Topic,
                                    QOS = routeAttribute.QOS
                                });
                        }
                    }
                }
            });
        }
        public bool AddRoute(string Topic, string Method, Type type, QOSEnum qos = QOSEnum.ExactlyOnce)
        {
            try
            {
                var method = type.GetMethods().Where(s => s.Name == Method).FirstOrDefault();
                if (method == null) return false;
                if (routes.Any(s => s.Topic == Topic && s.QOS == qos))
                    return false;
                routes.Add(new RouteHandleModel()
                {
                    ClassName = type.Name,
                    NameSpace = type.Namespace,
                    Method = method,
                    Topic = Topic,
                    QOS = qos
                });
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool ActionRoute(string Topic, byte[] Payload,IMQTTNetClient client)
        {
            try
            {
                var Route = routes.Where(s => s.Topic.TopicMatch(Topic)).ToList();
                Route = Route.Where(s => s != null &&
                s.Method != null
                &&
                s.Method.DeclaringType.IsInherit(typeof(ReceivedModel))).ToList();
                if (Route == null || Route.Count == 0)
                    return false;
                string str = Encoding.UTF8.GetString(Payload);
                ReceivedModel model = new ReceivedModel()
                {
                    Topic = Topic,
                    Payload = Payload,
                    Content = str,
                    Client= client
                };
                Route.ForEach(s => ActionHandle(s, model));
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }
        private void ActionHandle(RouteHandleModel _route, ReceivedModel model)
        {
            object instance = Activator.CreateInstance(_route.Method?.DeclaringType);
            if (instance == null) return;
            Type? baseType = _route.Method?.DeclaringType?.BaseType;
            if (baseType == null) return;
            PropertyInfo[] properties = baseType.GetProperties();
            foreach (PropertyInfo property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(model));
            MethodInfo? Method = _route.Method;
            if (Method == null) return;
            bool isJson = model.Content.IsJson();
            var Dec_param = _route.Method?.GetParameters();
            List<object?> param = new List<object?>();
            if (Dec_param == null || Dec_param.Length <= 0)
                this.Action(_route.Method, instance, param.ToArray());
            else if (!isJson)
            {
                foreach (var item in Dec_param)
                {
                    int index = Array.IndexOf(Dec_param, item);
                    if (index == 0)
                        param.Add(model.Content.StrToObjectParam(item));
                    else
                        param.Add(string.Empty);
                }
                this.Action(_route.Method, instance, param.ToArray());
            }
            else
            {
                Dictionary<string, object?> dic = new Dictionary<string, object?>();
                if (model.Content.IsJson())
                    dic = model.Content?.JsonToObject<Dictionary<string, object?>>() ?? new Dictionary<string, object?>();
                else
                    dic.Add(Dec_param[0].Name, model.Content.JsonToObject(Dec_param[0].ParameterType));

                foreach (var item in Dec_param)
                {
                    var _param = dic.Where(s => s.Key.ToLower() == item.Name.ToLower()).FirstOrDefault().Value;
                    if (_param == null)
                        param.Add((model.Content??string.Empty).StrToObjectParam(item));
                    else
                        param.Add(_param.ObjectToObjectParam(item));
                }
                this.Action(_route.Method, instance, param.ToArray());
                return;
            }

        }
        private void Action(MethodInfo? Method,object instance, object?[] param)
        {
            if (Method == null) return;
            if (Method.ReturnType.IsGenericType && (Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)|| Method.ReturnType.GetGenericTypeDefinition()==typeof(Task)))
            {

                var task = (Task)(Method.Invoke(instance, param));
                task?.Wait();
            }
            else
                Method.Invoke(instance, param);
        }
    }
}
